# Copyright 2012- by iNeed,Inc. All Rights Reserved.
#-*- encoding:utf-8 -*-
"""
"""
import sys, os, time
from common import *
DEFAULT_GRIDWIDTH  = 1100 #meter
DEFAULT_GRIDHEIGHT = 1110
gGridManager = None

def getGridsByVector(y,x,prevGrid):
     
     result = [prevGrid]
     oriy,orix =prevGrid
     mag = round(math.sqrt(x**2 + y**2))
     if mag:
         x2 = float(x)/mag
         y2 = float(y)/mag

         idx = 0
         while idx<mag:
            idx += 1
            y3 = y2*idx
            x3 = x2*idx
            """
            if y3<0:
                y3=0
            if x3<0:
                x3=0
            """
            newGrid = (int(round(oriy+y3)),int(round(orix+x3)))
            if newGrid!=prevGrid:
                    prevGrid=newGrid
                    result.append(prevGrid)
     return result
 

    
getGridPattern=re.compile(r"(\d+),(\d+),(\d+)")
def getGrid(str1):
    m = getGridPattern.search(str1)
    if m:
        return [int(m.group(1)),int(m.group(2)),int(m.group(3))]
    else:
        raise Exception,"not found %s"%str1

def getGridListByRouting(routing2):
    list1 = routing2.split('-')
    return [getGrid(item) for item in list1]
     
def getTheTestGridManager():#this is for test with the game
    global gGridManager
    if gGridManager==None:
        import game
        """
        for test, divide the game map to 4 square
        """
        pixNumW=213
        pixNumH=213
        if game.isTestWithWeb():
            top,left  = game.getLocation2(0,0)[:2]
            bot,right = game.getLocation2(game.MAP_HEIGHT,game.MAP_WIDTH)[:2]
            
            gGridManager = gridManager(FakeOwner(),left,top,right,bot,game.longtidue_scale*pixNumW,game.latitude_scale*pixNumH)
        else:
            left,top  = 0,0
            right,bot = game.MAP_WIDTH,game.MAP_HEIGHT
            gGridManager = gridManager(FakeOwner(),left,top,right,bot,pixNumW,pixNumH)
    return   gGridManager
  
gridManagerFactory = getTheTestGridManager
class gridManager(object):
    """
    gridManager manages the grids of one area, which can be a city, a CBD or ...
    """ 
    def __init__(self,owner,left,top,right,bot,gridWidth=DEFAULT_GRIDWIDTH,gridHeight=DEFAULT_GRIDHEIGHT):
        self.left = left
        self.top  = top
        self.right  = right
        self.bot    = bot
        self.width  = right-left
        self.height = top-bot
        if self.height<0:#only for game test
            self.height = -self.height
              
        self.gridWidth  = gridWidth
        self.gridHeight = gridHeight
        self.widthByGrid = int(round(self.width/gridWidth))
        self.heightByGrid= int(round(self.height/gridHeight))
        self.gridArray  = [[0 for col in range(self.widthByGrid)] for row in range(self.heightByGrid)]
        self.owner = owner
        self.log   = getLogger("iNeed.GridManager",os.path.join(CENTER_LOG_PATH,"GridManager.txt"))#owner.log
        
    
    
    
    def getDumpInfoByID(self,id,isOffer): 
        row = 0
        #self.log.debug("@begin check the grids")
        result = []
        while row<self.heightByGrid:
            col=0
            while col<self.widthByGrid:
                try:
                    grid =  self.gridArray[row][col]
                    if grid:
                        if isOffer:
                            dict1 = grid.offerQueue.queue
                            for idx,offerQueue in dict1.items():
                                for offer in offerQueue:
                                    if offer.id==id and offer.status==EDC_ACTIVE:
                                        if offer.destGrid:
                                            destGrid= offer.destGrid.getRowCol()
                                        else:
                                            destGrid="$"
                                        curGrid = offer.curGrid.getRowCol()
                                        value = "i=%d,%s->%s,s=%d,r=%d"%(offer.timeIdx,str(curGrid),str(destGrid),offer.seats,offer.rejectCount)
                                        result.append(value) 
                        else:
                            dict1 = grid.requestQueue.queue
                            for idx,requestQueue in dict1.items():
                                for request in requestQueue:
                                    if request.id==id:
                                        value ="%d:i=%d,%s->%s,s=%d"%(request.pubID,request.timeIdx,\
                                                                                       str(request.gridIdx),\
                                                                                       str(request.gridIdx2),request.passengerNum)
                                        result.append(value) 
                                
                except:
                    self.log.exception("(%d,%d) out of range"%(row,col))
                col += 1
            row += 1
        return result              
    def  getTheGrid(self,lat,longt):
        """
        find the grid where the (lat,longt) locates
        """ 
        col = int((longt-self.left)/self.gridWidth)
        row = self.top-lat
        if row<0:#only for game test
            row=-row
        row = int(row/self.gridHeight)
        try:
            #self.log.debug("out of range (%d,%d)->(%d,%d)"%(lat,longt,row,col))
            if self.gridArray[row][col]==0:
                self.gridArray[row][col] = grid(self,row,col)
        except:
            self.log.exception("out of range (%d,%d)->(%d,%d)"%(lat,longt,row,col))
            return None
        return self.gridArray[row][col]
    
    def  getAdjacentGridsRange(self,row,col,num):
        top = row - num
        if top<0:
            top=0
        bot = row + num
        left= col - num
        if left<0:
            left=0 
        right=col + num
        if bot>=self.heightByGrid:
            bot = self.heightByGrid-1
        if right>=self.widthByGrid:
            right = self.widthByGrid-1
       
       
        return top,left,bot,right
    def  getAdjacentGrids(self,row,col,num):
        
        j,i,bot,right = self.getAdjacentGridsRange(row, col, num)
        result = []   
        while j<bot:
            while i<right:
                if self.gridArray[j][i]:
                    result.append(self.gridArray[j][i])
        return result
    
    def  _getGridsIn2Points(self,prevLocation,location,prevGrid):
            row,col = self.getTheGrid(location[0],location[1]).getRowCol()
            #y= float(location[0]-prevLocation[0])/self.gridHeight
            #x= float(location[1]-prevLocation[1])/self.gridWidth
            y = row - prevGrid[0]
            x = col - prevGrid[1]
            result = getGridsByVector(y,x,prevGrid)
            return result
    
    def  _getRoutingIn2Points(self,gridsList,prevGrid,routing2,time1=0,timePerGrid=0):
            
            for grid in gridsList:
                #grid = grid[0]+item[0],grid[1]+item[1]
                #print grid
                if  prevGrid != grid:
                    routing2 += "-" + str(grid[0]) + ",%s"%str(grid[1])+",%d"%time1
                    time1 += timePerGrid
                    prevGrid = grid
            return routing2,prevGrid
        
    def  getGridsRoutingIn2Points(self,prevLocation,location,prevGrid):
            time1 = 0
            routing2=str(prevGrid[0]) + ",%s"%str(prevGrid[1]) + ",%d"%time1
            result = self._getGridsIn2Points(prevLocation,location,prevGrid)
            return self._getRoutingIn2Points(result,prevGrid,routing2)[0]
            
    def  getGridsRouting(self,routingList,prevGrid,prevLocation):
        time1 = 0
        routing2 = str(prevGrid[0]) + ",%s"%str(prevGrid[1]) + ",%d"%time1
        firstGrid = prevGrid
        scale = configall.LngLatSCALE
        #totoalNum = len(routingList)
        timePerGrid = configall.DEFAULT_TIME_PERGRID
        for idx,value in enumerate(routingList):
            #print "(%d,%d)->(%d,%d),duaration=%d\n"%(step[1]["lat"]*scale,step[1]["lng"]*scale,step[2]["lat"]*scale,step[2]["lng"]*scale,step[3])
            step,time2 = value
            time1 += time2
            location = step["lat"]*scale,step["lng"]*scale
            result = self._getGridsIn2Points(prevLocation,location,prevGrid)
            if result:#  and idx!=(totoalNum-1):
                timePerGrid = (routingList[idx][1])/len(result)
                routing2,newGrid = self._getRoutingIn2Points(result,prevGrid,routing2,time1,timePerGrid)
                if newGrid!=prevGrid:
                    #routing2 = routing
                    prevGrid = newGrid
            prevLocation=location 
        return   routing2
    


            
class OfferQueue(object):
    """
    """
    def __init__(self,grid):
        self.grid = grid 
        self.lock = threading.Condition()   
        self.queue  ={}
        self.log  = grid.log
        
    def add(self,capability):
        with AutoLock(self,self.lock):
            idx = capability.timeIdx
            
            if not self.queue.has_key(idx):
                self.queue[idx]=[]
            self.queue[idx].append(capability) 
            configall.matchEngine.addOffer(idx,capability)
   
    def delete(self,capability): 
        with AutoLock(self,self.lock):
            idx = capability.timeIdx
            if capability in self.queue[idx]:
                self.queue[idx].remove(capability)  

class PoolingGroup(object):    
    def __init__(self,request,destGridIdx): 
        self.mainRequest  = request
        self.requestList  = []
        self.number = 0
        self.dest = destGridIdx
        self.poolingStr =""
    def addOneRequest(self,request):
        
        self.requestList.append(request)
        self.number     += request.passengerNum
        self.poolingStr +="%d,"%request.pubID
        
    def getPoolingIDStr(self):
        return  self.poolingStr[:-1]   
    
    def isFullDrive(self):
        return self.number==configall.MAXPERSON
    
    def canAcceptNewRequest(self,num):
        result = self.mainRequest.status ==EPS_WAIT_CANDIDATES and self.number + num <= configall.MAXPERSON
        return result
                       
class RequestQueue(object):
    """
    the core is to aggregate the pooling.
    TODO: current solution is to  
    """
    def __init__(self,grid):
        self.lock   = threading.Condition()
        self.poolingGroupQueue   =  {}#{{}}
       
        self.queue   =   {}
        self.grid = grid
        self.log  = grid.log
            
    def add(self,request):  
        with AutoLock(self,self.lock): 
            type = request.type 
            idx  = request.timeIdx
            
            if not self.queue.has_key(idx):
                self.queue[idx]=[]
            self.queue[idx].append(request) 
            if type&EST_POOLING:
                self._groupPooling(request) 
    
    
    def delete(self,request):  
        with AutoLock(self,self.lock):
            idx = request.timeIdx
            if request in self.queue[idx]:
                self.queue[idx].remove(request) 
            
    
    def _groupPooling(self,request):            
        """
        TODO:apply the new matching algorithm,currently, strict (departTime,departGrid) and (destGrid)
        """    
        idx  = request.timeIdx
        #gridIdx = request.gridIdx
        gridIdx2 = request.gridIdx2
        key      = idx#,gridIdx[0],gridIdx[1],gridIdx2[0],gridIdx2[1])
        if not self.poolingGroupQueue.has_key(key):
            pg = PoolingGroup(request,gridIdx2)
            self.poolingGroupQueue[key]=[pg]
        else:
            for pg in self.poolingGroupQueue[key]:
                if pg.dest==gridIdx2 and pg.canAcceptNewRequest(request.passengerNum):
                    pg.addOneRequest(request)
                else:
                    pg = PoolingGroup(request,gridIdx2)
                    
            
class grid(object):
    """
    grid maintains two kinds of queues.
    for driver:
    short term:queue and queue
    long  term:ltDriverQueue
    for passenger:
    long term: ltPassengerQueue
    TODO: for short term,implement different order strategy for queue,currently, FIFO by default 
    ordered by fair strategy should be taken into account in future
    
    for long term, the queue is a hash table,the key is time and the value is the queue of driver or 
    passenger.
    gridManager will check the passenger queue and aggregates the request to pooling order as possible as it
    can.
    """
    def __init__(self,owner,row,col):
        
        
        self.row = row
        self.col = col
        #self.lock   = threading.Condition()
        self.owner = owner
        self.log   = owner.log
        self.requestQueue    = RequestQueue(self)
        self.offerQueue      = OfferQueue(self)
        
    def getRowCol(self):
        return (self.row,self.col)
   
    def _dumpQueues(self):
        pass
    
    def addRequest(self,request):
        self.requestQueue.add(request)
    
    def deleteRequest(self,request):
        self.requestQueue.delete(request) 
    
    def addOffer(self,offer):
        self.offerQueue.add(offer)
    
    def deleteOffer(self,offer):
        self.offerQueue.delete(offer)                     
    
    
            
if __name__=="__main__":
    print getGridsByVector(4,3,(5,5))
    print getGridsByVector(-1,1,(1,0))